package functionTypes;

import java.util.Random;

import Abstracts.AbstractGeneticFunction;
import Abstracts.AbstractSinglePropertyFunction;

public class Interpolation implements AbstractGeneticFunction, Cloneable {

	public AbstractSinglePropertyFunction[] subfunctions;
	int samplesCount = 10;
	double leftBorder = 20;
	double rightBorder = 100;
	double[] a;
	double[] b;
	double[] c;
	double[] d;
	double[] x;

	public Interpolation clone() throws CloneNotSupportedException {
		Interpolation obj = (Interpolation) super.clone();
		obj.subfunctions = new AbstractSinglePropertyFunction[subfunctions.length];
		for (int i = 0; i != subfunctions.length; ++i) {
			obj.subfunctions[i] = subfunctions[i].clone();
		}
		obj.samplesCount = samplesCount;
		obj.leftBorder = leftBorder;
		obj.rightBorder = rightBorder;
		obj.a = a.clone();
		obj.b = b.clone();
		obj.c = c.clone();
		obj.d = d.clone();
		obj.x = x.clone();
		return obj;
	}

	/*
	 * public void clone(AbstractFunction f){ f = new Interpolation();
	 * AbstractSinglePropertyFunction[] subs = new
	 * AbstractSinglePropertyFunction[this.subfunctions.length]; for (int
	 * i=0;i!=subs.length;++i){ subfunctions[i].clone(subs[i]); }
	 * f.createAbstractFunction(subs); ((Interpolation)f).samplesCount =
	 * this.samplesCount; ((Interpolation)f).leftBorder = this.leftBorder;
	 * ((Interpolation)f).rightBorder = this.rightBorder; }
	 */

	public void setBorders(double left, double right) throws Exception {
		if (left < right) {
			leftBorder = left;
			rightBorder = right;
		} else {
			throw new Exception("Left border shoud be lower than right");
		}
	}

	public Interpolation() {
		generateFunction();
	}

	public void bigMutate() {
		generateFunction();
	}

	public void mutate(double chance) {

		Random random = new Random();
		for (int i = 0; i != subfunctions.length; ++i) {
			double d = Math.max(Math.min(subfunctions[i].getCoeff()[0]
					+ random.nextGaussian() / 4, 1), 0);
			if (d != 0)
				if (d != 1)
					subfunctions[i].getCoeff()[0] = d;
		}
		
		calcCoeff();
		/*
		 * 
		 * for (int i = 0; i != subfunctions.length; ++i) { if (Math.random() <
		 * chance) subfunctions[i].getCoeff()[0] = Math.random(); }
		 */

	}

	public double compute(double[] params) {
		int n = samplesCount;
		double qwe = params[0];
		
		int s =0;
		
		if (qwe <= x[0]) {
			s = 1;
		} else if (qwe >= x[n - 1]) {
			s = n - 1;
		} else {
			int i = 0, j = n - 1;
			while (i + 1 < j) {
				int k = i + (j - i) / 2;
				if (qwe <= x[k])
					j = k;
				else
					i = k;
			}
			s = j;
		}

		double dx = qwe - x[s];
		return a[s] + (b[s] + (c[s] / 2.0 + d[s] * dx / 6.0) * dx) * dx;
	}

	public void createAbstractFunction(AbstractSinglePropertyFunction[] args) {
		subfunctions = args;
	}

	public void generateFunction() {
		subfunctions = new OldSinglePropertyFunction[samplesCount];
		a = new double[samplesCount];
		b = new double[samplesCount];
		c = new double[samplesCount];
		d = new double[samplesCount];
		x = new double[samplesCount];
		for (int i = 0; i != subfunctions.length; ++i) {
			subfunctions[i] = new OldSinglePropertyFunction();
			subfunctions[i].setConstantType();
		}
		calcCoeff();
	}

	public void calcCoeff() {
		int n = samplesCount;
		double step = (rightBorder - leftBorder) / (n - 1);
		for (int i = 0; i != n; ++i) {
			x[i] = n + step * i;
			a[i] = subfunctions[i].getCoeff()[0];
		}
		c[0] = 0;
		c[n - 1] = 0;
		double[] alpha = new double[n - 1];
		double[] beta = new double[n - 1];
		alpha[0] = beta[0] = 0.0;
		for (int i = 1; i < n - 1; ++i) {
			double h_i = x[i] - x[i - 1], h_i1 = x[i + 1] - x[i];
			double A = h_i;
			double C = 2.0 * (h_i + h_i1);
			double B = h_i1;
			double F = 6.0 * ((subfunctions[i + 1].getCoeff()[0] - subfunctions[i]
					.getCoeff()[0])
					/ h_i1 - (subfunctions[i].getCoeff()[0] - subfunctions[i - 1]
					.getCoeff()[0])
					/ h_i);
			double z = (A * alpha[i - 1] + C);
			alpha[i] = -B / z;
			beta[i] = (F - A * beta[i - 1]) / z;
		}

		for (int i = n - 2; i > 0; --i) {
			c[i] = alpha[i] * c[i + 1] + beta[i];
		}

		for (int i = n - 1; i > 0; --i) {
			double h_i = x[i] - x[i - 1];
			d[i] = (c[i] - c[i - 1]) / h_i;
			b[i] = h_i
					* (2.0 * c[i] + c[i - 1])
					/ 6.0
					+ (subfunctions[i].getCoeff()[0] - subfunctions[i - 1]
							.getCoeff()[0]) / h_i;
		}
	}

	public Interpolation[] crossover(Interpolation f)
			throws CloneNotSupportedException {
		Interpolation new1 = new Interpolation();
		Interpolation new2 = new Interpolation();
		int beginIndex1 = (int) (Math.random() * (samplesCount / 2));
		int beginIndex2 = (int) (Math.random() * (samplesCount / 2));
		int length = (int) (Math.random() * samplesCount / 4 + samplesCount / 4);
		int endIndex1 = beginIndex1 + length - 1;
		int endIndex2 = beginIndex2 + length - 1;

		for (int i = 0; i != subfunctions.length; ++i) {
			new1.subfunctions[i] = null;
			new2.subfunctions[i] = null;
		}

		int k = 0;

		for (int i = beginIndex1; i != endIndex1; ++i) {
			new1.subfunctions[i] = f.subfunctions[beginIndex2 + k].clone();
			++k;
		}

		k = 0;

		for (int i = beginIndex2; i != endIndex2; ++i) {
			new2.subfunctions[i] = subfunctions[beginIndex1 + k].clone();
			++k;
		}

		for (int i = 0; i != subfunctions.length; ++i) {
			if (new1.subfunctions[i] == null) {
				new1.subfunctions[i] = subfunctions[i].clone();
			}
			if (new2.subfunctions[i] == null) {
				new2.subfunctions[i] = f.subfunctions[i].clone();
			}
		}
		new1.calcCoeff();
		new2.calcCoeff();
		return new Interpolation[] { this, f, new1, new2 };
	}

	public AbstractSinglePropertyFunction[] getSubFunctions() {
		return subfunctions;
	}

}
